/*******************************************************************************
 * Copyright (c) 2011 Grupo de Sistemas Inteligentes (GSI) - DIT UPM
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package es.upm.dit.gsi.eclipse.jadex.adfmanager.wizards;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.runtime.*;
import org.eclipse.jface.operation.*;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.InvocationTargetException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.ui.*;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.ide.IDE;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import es.upm.dit.gsi.eclipse.jadex.adfmanager.wizards.AddPlanWizardPage1;
import es.upm.dit.gsi.eclipse.jadex.util.ImageFactory;

/**
 * Wizard for adding a new Plan to the selected agent
 * 
 * It has got 2 pages:
 *  - Page1: Agent selection and asking for automatic class generation
 *  - Page2: Plan details 
 * 
 * It supports automatic class generation of the needed java classes.
 *     
 * @author Pablo Muñoz
 * @TODO verify if the Plan exists in the selected file. Add support for triggers (no event support developed yet) and conditions * 
 */

public class AddPlanWizard extends Wizard implements INewWizard {
	private AddPlanWizardPage1 page1;
	private AddPlanWizardPage2 page2;
	private ParameterSelectionWizardPage page3;
	private ISelection selection;

	/**
	 * Constructor for Agent Wizard.
	 */
	public AddPlanWizard(ISelection selection) {
		super();
		setNeedsProgressMonitor(true);
		this.selection = selection;
		setDefaultPageImageDescriptor(ImageFactory.getImageDescriptor(ImageFactory.GSI_BANNER_SMALL));
	}
	
	/**
	 * Adding the page to the wizard.
	 */

	public void addPages() {
		page1 = new AddPlanWizardPage1(selection);
		page1.setTitle("Add Plan Wizard");
		page2 = new AddPlanWizardPage2(selection);
		page2.setTitle("Add Plan Wizard");
		page3 = new ParameterSelectionWizardPage(selection);
		page3.setTitle("Add Plan Wizard");
		addPage(page1);
		addPage(page2);
		addPage(page3);
	}

	/**
	 * This method is called when 'Finish' button is pressed in
	 * the wizard. We will create an operation and run it
	 * using wizard as execution context.
	 */
	public boolean performFinish() {
		final IFile agentFile = page1.getAgentFile();
		final IFolder sourceFolder = page1.getSourceFolder();
		final String packageName = page1.getPackageName();
		final String planName = page2.getPlanName();
		final String planPriority = page2.getPlanPriority();
		final String planClassName = page1.getPlanClassName();
		final String planFileName = page1.getPlanFileName();
		final String planType = page2.getPlanType();
		
		//Goal parameters
		final Parameter[] parameters = page3.getParameters();
		
		IRunnableWithProgress op = new IRunnableWithProgress() {
			public void run(IProgressMonitor monitor) throws InvocationTargetException {
				try {
					doFinish(agentFile, sourceFolder, packageName,planName, planPriority, planClassName, planFileName, planType, parameters, monitor);
				} catch (CoreException e) {
					throw new InvocationTargetException(e);
				} finally {
					monitor.done();
				}
			}
		};
		
		try {
			getContainer().run(true, false, op);
		} catch (InterruptedException e) {
			return false;
		} catch (InvocationTargetException e) {
			Throwable realException = e.getTargetException();
			MessageDialog.openError(getShell(), "Error", realException.getMessage());
			return false;
		}
		return true;
	}
	
	/**
	 * The worker method. It will find the container, create the
	 * file if missing or just replace its contents, and open
	 * the editor on the newly created file.
	 */
	private void doFinish(
		IFile agentFile,
		IFolder sourceFolder,
		String packageName,
		String planName,
		String planPriority,
		String planClassName,
		String planFileName,
		String planType,
		Parameter[] parameters,
		IProgressMonitor monitor)
		throws CoreException {
		monitor.beginTask("Modificating ADF File", 2);
		
		try{
		
		DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		Document doc = docBuilder.parse(agentFile.getRawLocation().toOSString());
	 
		//Get the plans element 
		Node plans = doc.getElementsByTagName("plans").item(0);
		
		Element newPlan = doc.createElement("plan");
		newPlan.setAttribute("name", planName);
		
		if(planPriority != null) newPlan.setAttribute("priority", planPriority);
		Element body = doc.createElement("body");
		if(planClassName != null) body.setAttribute("class", planClassName);
		//if(planFileName != null) body.setAttribute("impl", planFileName);
		if(planType != null) body.setAttribute("type", planType);
		
		newPlan.appendChild(body);
		
		//Set the Plan Parameters
		if(parameters != null){
			for(Parameter p : parameters){
				Element parameter = doc.createElement("parameter");
				parameter.setAttribute("name", p.get_name());
				parameter.setAttribute("class", p.get_class());
				parameter.setAttribute("description", p.get_description());
				parameter.setAttribute("direction",p.get_direction());
				parameter.setAttribute("dynamic", p.get_dynamic());
				parameter.setAttribute("exported",p.get_exported());
				parameter.setAttribute("optional", p.get_optional());
				parameter.setAttribute("transient",p.get_transient());
				parameter.setAttribute("updaterate", p.get_updaterate());
				if(p.get_value().length() > 0){
					Element value = doc.createElement("value");
					value.setTextContent(p.get_value());
					parameter.appendChild(value);
				}
				newPlan.appendChild(parameter);
			}
		}
			
		plans.appendChild(newPlan);
		
		TransformerFactory transformerFactory = TransformerFactory.newInstance(); 
	    transformerFactory.setAttribute("indent-number", new Integer(4));
		Transformer transformer = transformerFactory.newTransformer();
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
	    DOMSource source = new DOMSource(doc);
	    StreamResult result =  new StreamResult(new OutputStreamWriter(
	    		new FileOutputStream(new File(agentFile.getRawLocation().toOSString())),"UTF-8"));
	    transformer.transform(source,result);		
		}
		catch(Exception e1){
			e1.printStackTrace();
		}
	    System.out.println("Done");
	    
	    if(planClassName != null){
	    	IJavaProject project = JavaCore.create(agentFile.getProject());
	    	IPackageFragmentRoot rootFragment = project.getPackageFragmentRoot(sourceFolder);
	    	IPackageFragment fragment = rootFragment.createPackageFragment(packageName, false,monitor);
	    
	    	String source = getPlanSource(fragment.getElementName(),planClassName);	    
	    	final ICompilationUnit cu = fragment.createCompilationUnit(planFileName, source, false, null);
	    	cu.getResource().setDerived(true, monitor);
	    
	    	monitor.setTaskName("Opening file for editing...");
		
	    	Display.getDefault ().asyncExec
	    	(new Runnable ()
		    	{
		        	public void run ()
		        	{
		        		try
		        		{		        			
		        			JavaUI.openInEditor (cu);
		        		}
		        		catch (Exception e)
		        		{
		                e.printStackTrace ();
		            }
		        }
		    }
	    	);	
	    	cu.close();
	    }
	    
	    final IFile file = agentFile;
	    getShell().getDisplay().asyncExec(new Runnable() {
			public void run() {
				IWorkbenchPage page =
					PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
				try {
					IEditorPart editor = IDE.openEditor(page, file, true);
					ICommandService cmdService = (ICommandService) editor.getSite().getService(ICommandService.class);
					Command format = cmdService.getCommand("org.eclipse.wst.sse.ui.format.document");
					if (format.isDefined()) {
						try {
							format.executeWithChecks(new ExecutionEvent());
						} 
						catch(Exception e1){
							}
						}
				} catch (PartInitException e) {
				}
			}
		});
	    
	    agentFile.refreshLocal(IFile.DEPTH_INFINITE, monitor);
	    monitor.worked(1);
	}
	
	private String getPlanSource(String packageName, String planName){
		 StringBuffer source = new StringBuffer();
		 source.append("package " + packageName + ";\n");
		 source.append("\n");
		 source.append("import jadex.bdi.runtime.Plan" + ";\n");
		 source.append("\n");
		 source.append("public class " + planName + " extends Plan {" + "\n");
		 source.append("\t" + "public void body(){" + "\n");
		 source.append("\t" + "\t" + "/*" + "\n");
		 source.append("\t" + "\t" + "*  TO DO" + "\n");
		 source.append("\t" + "\t" + "*/" + "\n");
		 source.append("\t" + "}" + "\n");
		 source.append("}" );
		 return source.toString();
	}

	/**
	 * We will accept the selection in the workbench to see if
	 * we can initialize from it.
	 * @see IWorkbenchWizard#init(IWorkbench, IStructuredSelection)
	 */
	public void init(IWorkbench workbench, IStructuredSelection selection) {
		this.selection = selection;
	}
}